/*
 * Copyright (C) 2014 Sony Computer Entertainment Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "AsyncImageDecoder.h"

#include "BitmapImage.h"
#include "CachedImage.h"
#include "Frame.h"
#include "JSDOMWindow.h"
#include "JSAsyncImageDecoder.h"
#include "ResourceBuffer.h"
#include "ScriptState.h"
#include "Supplementable.h"

#include <manx/WorkQueue.h>
#include <wtf/Functional.h>
#include <wtf/MainThread.h>

using namespace WTF;
using namespace WebCore;


class AsyncImageDecoder::DOMWindowSupplement: public Supplement<DOMWindow> {
public:
    explicit DOMWindowSupplement(DOMWindow* window) : m_decoder(adoptRef(new AsyncImageDecoder(window))) {}

    static const char* supplementName() { return "AsyncImageDecoder"; }
    static AsyncImageDecoder* from(DOMWindow* window)
    {
        DOMWindowSupplement* supplement = static_cast<DOMWindowSupplement*>(Supplement<DOMWindow>::from(window, supplementName()));
        if (!supplement) {
            supplement = new DOMWindowSupplement(window);
            provideTo(window, supplementName(), adoptPtr(supplement));
        }
        return supplement->decoder();
    }

    AsyncImageDecoder* decoder() const { return m_decoder.get(); }
private:
    RefPtr<AsyncImageDecoder> m_decoder;
};


class AsyncImageDecoder::ImageDecodeJob : public Manx::WorkQueue::Function {
public:
    ImageDecodeJob(PassRefPtr<AsyncImageDecoder> parent, uint64_t id): m_parent(parent), m_id(id) {}
    virtual ~ImageDecodeJob() {
        callOnMainThread(bind(&AsyncImageDecoder::notifyDecodeJobComplete, m_parent, m_id));
    }
    virtual void call() const {
        // Triggers full decoding
        m_parent->bitmapForJob(m_id)->nativeImageForCurrentFrame();
    }
private:
    RefPtr<AsyncImageDecoder> m_parent;
    uint64_t m_id;
};


class AsyncImageDecoder::ImageDecodeJobData {
public:
    CachedResourceHandle<CachedImage> m_cachedImage;
    RefPtr<VoidCallback> m_jsCallback;

    // The following members are only modified by the worker thread
    RefPtr<BitmapImage> m_bitmapForJob;
};

AsyncImageDecoder::AsyncImageDecoder(DOMWindow* window)
    : DOMWindowProperty(window->frame())
    , m_workQueue(0)
    , m_nextJobID(1)
{
}

AsyncImageDecoder::~AsyncImageDecoder()
{
    ASSERT(m_pendingImageDecodes.isEmpty());

    if (m_workQueue)
        m_workQueue->destroy(); // deletes m_workQueue itself too
}

bool AsyncImageDecoder::registerOnFrame(const String& propertyName, Frame* frame, DOMWrapperWorld* world)
{
    JSDOMWindow* window = toJSDOMWindow(frame, world);
    ScriptState* exec = window->globalExec();

    window->putDirect(exec->vm(), JSC::Identifier(exec, propertyName), toJS(exec, window->globalObject(), DOMWindowSupplement::from(toDOMWindow(window))));

    return true;
}

void AsyncImageDecoder::decode(HTMLImageElement* imageElem, PassRefPtr<VoidCallback> callback)
{
    if (!imageElem || !imageElem->cachedImage()->hasImage() || !imageElem->cachedImage()->image()->isBitmapImage())
        return;

    // If the image is already decoded, do nothing (just call the callback right away if one is set)
    if (imageElem->cachedImage()->decodedSize()) {
        if (callback)
            callback->handleEvent();
        return;
    }


    uint64_t jobID = m_nextJobID++;

    // FIXME add proper Mutex locking
    ImageDecodeJobData* jobData = new ImageDecodeJobData;
    jobData->m_cachedImage = imageElem->cachedImage();
    jobData->m_bitmapForJob = BitmapImage::create();
    jobData->m_bitmapForJob->setData(imageElem->cachedImage()->resourceBuffer()->sharedBuffer(), true);
    jobData->m_jsCallback = callback;
    m_pendingImageDecodes.add(jobID, adoptPtr(jobData));

    workQueue().dispatchAfterDelay(new ImageDecodeJob(this, jobID), 0);
}

void AsyncImageDecoder::notifyDecodeJobComplete(uint64_t jobID)
{
    ASSERT(m_pendingImageDecodes.contains(jobID));

    ImageDecodeJobData* jobData = m_pendingImageDecodes.get(jobID);
    jobData->m_cachedImage->setDecodedImage(jobData->m_bitmapForJob);

    if (jobData->m_jsCallback)
        jobData->m_jsCallback->handleEvent();

    m_pendingImageDecodes.remove(jobID);
}

Manx::WorkQueue& AsyncImageDecoder::workQueue()
{
    if (!m_workQueue) {
        m_workQueue = Manx::WorkQueue::create("AsyncImageDecoder");
        m_workQueue->startThread();
    }
    return *m_workQueue;
}

BitmapImage* AsyncImageDecoder::bitmapForJob(uint64_t jobID)
{
    ASSERT(m_pendingImageDecodes.contains(jobID));

    return m_pendingImageDecodes.get(jobID)->m_bitmapForJob.get();
}

